package me.blankm.wslib;

import android.content.Context;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.text.TextUtils;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFactory;
import com.neovisionaries.ws.client.WebSocketFrame;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import me.blankm.wslib.common.CallbackDataWrapper;
import me.blankm.wslib.common.Codec;
import me.blankm.wslib.common.ErrorCode;
import me.blankm.wslib.common.TimeOutTask;
import me.blankm.wslib.icallback.ICallback;
import me.blankm.wslib.common.RequestCallBack;
import me.blankm.wslib.common.WsStatus;
import me.blankm.wslib.icallback.WebSocketCallback;
import me.blankm.wslib.notify.NotifyListenerManager;
import me.blankm.wslib.request.Request;
import me.blankm.wslib.response.Response;
import me.blankm.wslib.utils.WsLibLogUtils;

/**
 * Author by Mr.Meng
 * created 2021/11/16
 * <p>
 * https://blog.csdn.net/huangliniqng/article/details/105338690
 *
 * @desc ws管理类
 */
public class WsManager {
    private final String TAG = this.getClass().getSimpleName();

    private Context context;
    /**
     * WebSocket config
     */
    /*心跳ping-pong*/
    private static final String HEARTBEAT_FLAG = "app_heartbeat_flag";
    private static final long HEARTBEAT_INTERVAL = 10000;//心跳间隔
    /*帧队列最大值*/
    private static final int FRAME_QUEUE_SIZE = 5;
    /*WebSocketFactory连接超时时间*/
    private static final int CONNECT_TIMEOUT = 5000;
    /*请求超时时间*/
    private static final int REQUEST_TIMEOUT = 10000;
    /*测试服默认地址*/
    private static final String DEF_TEST_URL = "wss://livechat.ckcdn.cn:3838";
    /*正式服默认地址*/
    private static final String DEF_RELEASE_URL = "wss://livechat.ckcdn.cn:3838";
    private static final String DEF_URL = BuildConfig.DEBUG ? DEF_TEST_URL : DEF_RELEASE_URL;
    private String url;

    private WsStatus mStatus;
    private WebSocket ws;
    private WsListener mListener;
    /*每个请求的唯一标识*/
    private AtomicLong seqId = new AtomicLong(SystemClock.uptimeMillis());

    private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
    private Map<Long, TimeOutTask> callbacks = new HashMap<>();
    /**
     * 重连参数配置
     */
    /*重连次数*/
    private int reconnectCount = 0;
    /*重连最小时间间隔*/
    private final long minInterval = 3000;
    /*重连最大时间间隔*/
    private final long maxInterval = 60000;

    /*返回数据配置*/
    private final int SUCCESS_HANDLE = 0x01;
    private final int ERROR_HANDLE = 0x02;

    /*配置是否有消息确认机制*/
    private boolean isNeedTimeoutTask = false;

    /*回调事件 需要ws连接前设置*/
    private WebSocketCallback wsCallback;

    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SUCCESS_HANDLE:
                    CallbackDataWrapper successWrapper = (CallbackDataWrapper) msg.obj;
                    successWrapper.getCallback().onSuccess(successWrapper.getData());
                    break;
                case ERROR_HANDLE:
                    CallbackDataWrapper errorWrapper = (CallbackDataWrapper) msg.obj;
                    errorWrapper.getCallback().onFail((String) errorWrapper.getData());
                    break;
            }
        }
    };


    private WsManager() {
    }

    private static class WsManagerHolder {
        private static final WsManager mInstance = new WsManager();
    }

    public static WsManager getInstance() {
        return WsManagerHolder.mInstance;
    }


    /*需要在Application初始化*/
    public void init(Context context) {
        this.context = context;

        //注册广播接收器，接收CONNECTIVITY_CHANGE这个广播
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        NetStatusReceiver networkChangeReceiver = new NetStatusReceiver();
        context.registerReceiver(networkChangeReceiver, intentFilter);
    }

    /*配置是否有消息确认机制*/
    public void setNeedTimeoutTask(boolean isNeed) {
        isNeedTimeoutTask = isNeed;
    }

    /*需要在连接socket前设置*/
    public void setWsCallback(WebSocketCallback wsCallback) {
        this.wsCallback = wsCallback;
    }

    public void onConnect() {
        try {
            /**
             * configUrl其实是缓存在本地的连接地址
             * 这个缓存本地连接地址是app启动的时候通过http请求去服务端获取的,
             * 每次app启动的时候会拿当前时间与缓存时间比较,超过6小时就再次去服务端获取新的连接地址更新本地缓存
             */
            String configUrl = "";
            url = TextUtils.isEmpty(configUrl) ? DEF_URL : configUrl;

            ws = new WebSocketFactory().createSocket(url, CONNECT_TIMEOUT)
                    //设置帧队列最大值为5
                    .setFrameQueueSize(FRAME_QUEUE_SIZE)
                    //设置不允许服务端关闭连接却未发送关闭帧
                    .setMissingCloseFrameAllowed(false)
                    //添加回调监听
                    .addListener(mListener = new WsListener())
                    .connectAsynchronously();//异步连接
            setStatus(WsStatus.CONNECTING);
            WsLibLogUtils.i(TAG, "第一次连接");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /*登录鉴权*/
    private void doAuth() {

        Request request = new Request.Builder()
                .reqCount(0)
                .action("login")
                .build();


//        sendReq(request, null, new ResponseCallback() {
//            @Override
//            public void onSuccess(Object o) {
//                WsLibLogUtils.i(TAG, "授权成功");
//                setStatus(WsStatus.AUTH_SUCCESS);
//                startKeepAlive();
//                delaySyncData();
//            }
//
//
//            @Override
//            public void onFail(String msg) {
//
//            }
//        });
    }

    //同步数据
    private void delaySyncData() {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {

                Request request = new Request.Builder()
                        .reqCount(0)
                        .action("sync")
                        .build();


//                sendReq(request, new ReqArgsInfo(), new ResponseCallback<ReqArgsInfo>() {
//                    @Override
//                    public void onSuccess(ReqArgsInfo o) {
//
//                    }
//
//
//                    @Override
//                    public void onFail(String msg) {
//
//                    }
//                });
            }
        }, 300);
    }


    private int heartbeatFailCount = 0;
    private final Runnable heartbeatTask = new Runnable() {
        @Override
        public void run() {
            keepAlive();
            mHandler.removeCallbacks(heartbeatTask);
            mHandler.postDelayed(heartbeatTask, HEARTBEAT_INTERVAL);
        }
    };

    /*心跳触发事件*/
    private void keepAlive() {
        if (ws != null && ws.isOpen()) {
            WsLibLogUtils.i(TAG, "心跳");
            //发送ping事件 ==>发送心跳
            ws.sendPing(HEARTBEAT_FLAG);
        } else {
            heartbeatFailCount++;
            if (heartbeatFailCount >= 3) {
                reconnect();
            }
        }
    }

    /*开始心跳*/
    public void startKeepAlive() {
        mHandler.postDelayed(heartbeatTask, HEARTBEAT_INTERVAL);
    }


    /*取消心跳*/
    private void cancelKeepAlive() {
        heartbeatFailCount = 0;
        mHandler.removeCallbacks(heartbeatTask);
    }


    public void sendMessage(Request request) {
        sendMessage(request, null);
    }

    public void sendMessage(Request request, ICallback callback) {
        sendMessage(request, callback, REQUEST_TIMEOUT);
    }

    public void sendMessage(Request text, ICallback callback, long timeout) {
        sendMessage(text, callback, REQUEST_TIMEOUT, 1);
    }

    /**
     * 发送信息
     *
     * @param req      发送内容
     * @param callback 回调函数
     * @param timeout  超时时间
     * @param reqCount 请求次数
     */
    public <T> void sendMessage(Request req, ICallback callback, long timeout, int reqCount) {
        if (!isNetConnect() && callback != null) {
            callback.onFail("网络不可用");
            return;
        }
        if (WsStatus.AUTH_SUCCESS.equals(getStatus())) {
            Request request = new Request.Builder<T>()
                    .action(req.getAction())
                    .seqId(seqId.getAndIncrement())
                    .reqCount(reqCount)
                    .reqArgs(req.getReq())
                    .build();

            ScheduledFuture timeoutTask = enqueueTimeout(request.getSeqId(), timeout);//添加超时任务

            RequestCallBack tempCallback = new RequestCallBack<String>() {

                @Override
                public void onSuccess(String o) {
                    mHandler.obtainMessage(SUCCESS_HANDLE, new CallbackDataWrapper(callback, o))
                            .sendToTarget();
                }


                @Override
                public void onError(String msg, Request request) {
                    mHandler.obtainMessage(ERROR_HANDLE, new CallbackDataWrapper(callback, msg))
                            .sendToTarget();
                }


                @Override
                public void onTimeout(Request request) {
                    timeoutHandle(request, callback, timeout);
                }
            };

            callbacks.put(request.getSeqId(),
                    new TimeOutTask(request, tempCallback, timeoutTask));

            WsLibLogUtils.i(TAG, "send text " + new Gson().toJson(request));
            if (ws != null && ws.isOpen()) {
                ws.sendText(new Gson().toJson(request));
            } else {
                if (callback != null) {
                    callback.onFail("socket未连接");
                }
            }
        } else {
            if (callback != null) {
                callback.onFail("信息发送失败");
            }
        }
    }


    /**
     * 继承默认的监听空实现WebSocketAdapter,重写我们需要的方法
     * onTextMessage 收到文字信息
     * onConnected 连接成功
     * onConnectError 连接失败
     * onDisconnected 连接关闭
     */
    class WsListener extends WebSocketAdapter {
        @Override
        public void onTextMessage(WebSocket websocket, String text) throws Exception {
            super.onTextMessage(websocket, text);
            WsLibLogUtils.i(TAG, "receiverMsg===>" + text);

            // 请求是否需要消息确认机制
            if (isNeedTimeoutTask) {
                Response response = Codec.decoder(text);//解析出第一层bean
                if (TextUtils.equals(response.getAction(), "message")) {//响应
                    TimeOutTask wrapper = callbacks.remove(
                            Long.parseLong(response.getSeqId()));//找到对应callback
                    if (wrapper == null) {
                        //超时任务没有此项任务，就直接回调数据
                        WsLibLogUtils.i(TAG, "(action:" + response.getAction() + ") not found callback");
                        if (wsCallback != null) {
                            wsCallback.onReceivedMessage(text);
                        }
                        return;
                    }

                    try {
                        wrapper.getTimeoutTask().cancel(true);//取消超时任务
                        if (response.isOk()) {
                            // 回调相应数据
                            if (TextUtils.isEmpty(response.getResp())) {
                                wrapper.getTempCallback()
                                        .onError(ErrorCode.EMPTY_DATA.getMsg(), wrapper.getRequest());
                            } else {
                                wrapper.getTempCallback().onSuccess(response.getResp());
                            }
                        } else {
                            wrapper.getTempCallback()
                                    .onError(ErrorCode.BUSINESS_EXCEPTION.getMsg(), wrapper.getRequest());
                        }
                    } catch (JsonSyntaxException e) {
                        e.printStackTrace();
                        wrapper.getTempCallback()
                                .onError(ErrorCode.PARSE_EXCEPTION.getMsg(), wrapper.getRequest());
                    }

                } else if (TextUtils.equals(response.getAction(), "notify")) {//通知
                    NotifyListenerManager.getInstance().fire(response);
                }
            } else {
                //如果不需要确认机制，直接回调数据
                if (wsCallback != null) {
                    wsCallback.onReceivedMessage(text);
                }
            }

        }


        @Override
        public void onConnected(WebSocket websocket, Map<String, List<String>> headers)
                throws Exception {
            super.onConnected(websocket, headers);
            WsLibLogUtils.i(TAG, "WebSocket 当前连接状态：连接成功");
            setStatus(WsStatus.CONNECT_SUCCESS);
            cancelReconnect();//连接成功的时候取消重连,初始化连接次数
            //登录授权
            if (wsCallback != null) {
                wsCallback.onLoginAuth();
            }
        }


        @Override
        public void onConnectError(WebSocket websocket, WebSocketException exception)
                throws Exception {
            super.onConnectError(websocket, exception);
            WsLibLogUtils.i(TAG, "WebSocket 当前连接状态：连接错误");
            setStatus(WsStatus.CONNECT_FAIL);
            reconnect();//连接错误的时候调用重连方法
        }


        @Override
        public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, WebSocketFrame clientCloseFrame, boolean closedByServer)
                throws Exception {
            super.onDisconnected(websocket, serverCloseFrame, clientCloseFrame, closedByServer);
            WsLibLogUtils.i(TAG, "WebSocket 当前连接状态：断开连接");
            setStatus(WsStatus.CONNECT_FAIL);
            reconnect();//连接断开的时候调用重连方法
        }

        @Override
        public void onPingFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
            super.onPingFrame(websocket, frame);

        }

        @Override
        public void onPongFrame(WebSocket websocket, WebSocketFrame frame) throws Exception {
            super.onPongFrame(websocket, frame);
            WsLibLogUtils.i(TAG, "收到ping标记：" + frame.getPayloadText());
            if (TextUtils.equals(frame.getPayloadText(), HEARTBEAT_FLAG)) {
                //心跳次数赋值0
                heartbeatFailCount = 0;
                //心跳事件回调
                if (wsCallback != null) {
                    wsCallback.sendHeartEvent();
                }
            }

        }
    }


    /*设置当前登录状态*/
    public void setStatus(WsStatus status) {
        this.mStatus = status;
    }


    private WsStatus getStatus() {
        return mStatus;
    }


    public void disconnect() {
        if (ws != null) {
            ws.disconnect();
        }
    }


    /*重连*/
    public void reconnect() {
        if (!isNetConnect()) {
            reconnectCount = 0;
            WsLibLogUtils.i(TAG, "重连失败网络不可用");
            return;
        }

        //这里其实应该还有个用户是否登录了的判断 因为当连接成功后我们需要发送用户信息到服务端进行校验
        //由于我们这里是个demo所以省略了
        if (ws != null &&
                !ws.isOpen() &&//当前连接断开了
                getStatus() != WsStatus.CONNECTING) {//不是正在重连状态
            reconnectCount++;
            setStatus(WsStatus.CONNECTING);
            cancelKeepAlive();
            long reconnectTime = minInterval;
            if (reconnectCount > 3) {
                url = DEF_URL;
                long temp = minInterval * (reconnectCount - 2);
                reconnectTime = Math.min(temp, maxInterval);
            }
            WsLibLogUtils.i(TAG, "准备开始第" + reconnectCount + "次重连,重连间隔" + reconnectTime + " -- url:" + url);
            mHandler.postDelayed(mReconnectTask, reconnectTime);
        } else {
            WsLibLogUtils.i(TAG, "正在连接中，无需连接");
        }
    }


    private final Runnable mReconnectTask = new Runnable() {

        @Override
        public void run() {
            try {
                ws = new WebSocketFactory().createSocket(url, CONNECT_TIMEOUT)
                        //设置帧队列最大值为5
                        .setFrameQueueSize(FRAME_QUEUE_SIZE)
                        //设置不允许服务端关闭连接却未发送关闭帧
                        //设置是否允许服务器在不向客户端发送关闭帧的情况下关闭WebSocket连接。
                        .setMissingCloseFrameAllowed(false)
                        //添加回调监听
                        .addListener(mListener = new WsListener())
                        .connectAsynchronously();//异步连接
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };


    private void cancelReconnect() {
        reconnectCount = 0;
        mHandler.removeCallbacks(mReconnectTask);
    }


    private boolean isNetConnect() {
        ConnectivityManager connectivity = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivity != null) {
            NetworkInfo info = connectivity.getActiveNetworkInfo();
            if (info != null && info.isConnected()) {
                // 当前网络是连接的
                if (info.getState() == NetworkInfo.State.CONNECTED) {
                    // 当前所连接的网络可用
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * 添加超时任务
     */
    private ScheduledFuture enqueueTimeout(final long seqId, long timeout) {
        return executor.schedule(new Runnable() {
            @Override
            public void run() {
                TimeOutTask wrapper = callbacks.remove(seqId);
                if (wrapper != null) {
                    WsLibLogUtils.i(TAG, "(action:" + wrapper.getRequest().getAction() + ")第" + wrapper.getRequest().getReqCount() + "次请求超时");
                    wrapper.getTempCallback().onTimeout(wrapper.getRequest());
                }
            }
        }, timeout, TimeUnit.MILLISECONDS);
    }


    /**
     * 超时处理
     */
    private void timeoutHandle(Request request, ICallback callback, long timeout) {
        if (request.getReqCount() > 3) {
            WsLibLogUtils.i(TAG, "(action:" + request.getAction() + ")连续3次请求超时 执行http请求");
            //走http请求
        } else {
            sendMessage(request, callback, timeout, request.getReqCount() + 1);
            WsLibLogUtils.i(TAG, "(action:" + request.getAction() + ")发起第" + request.getReqCount() + "次请求");
        }
    }

}
